# Ultralytics YOLO 🚀, AGPL-3.0 license

import torch
from torch.nn.functional import interpolate
from ultralytics.engine.results import Results
from predictor.detection_predictor import DetectionPredictor
from predictor.ops import non_max_suppression
from ultralytics.utils import DEFAULT_CFG, ops


class SegmentationPredictor(DetectionPredictor):
    """
    A class extending the DetectionPredictor class for prediction based on a segmentation model.

    Example:
        ```python
        from ultralytics.utils import ASSETS
        from ultralytics.models.yolo.segment import SegmentationPredictor

        args = dict(model='yolov8n-seg.pt', source=ASSETS)
        predictor = SegmentationPredictor(overrides=args)
        predictor.predict_cli()
        ```
    """

    def __init__(self, cfg=DEFAULT_CFG, overrides=None, _callbacks=None):
        """Initializes the SegmentationPredictor with the provided configuration, overrides, and callbacks."""
        super().__init__(cfg, overrides, _callbacks)
        self.args.task = "segment"

    def postprocess(self, preds, img, orig_imgs, orig_imgs_l=None):
        """Applies non-max suppression and processes detections for each image in an input batch."""
        p = non_max_suppression(
            preds[0],
            self.args.conf,
            self.args.iou,
            agnostic=self.args.agnostic_nms,
            max_det=self.args.max_det,
            nc=len(self.model.names),
            classes=self.args.classes,
        )

        if not isinstance(orig_imgs, list):  # input images are a torch.Tensor, not a list
            orig_imgs = ops.convert_torch2numpy_batch(orig_imgs)

        results = []
        proto = preds[1][-1] if isinstance(preds[1], tuple) else preds[1]  # tuple if PyTorch model or array if exported
        # 【兼容】
        proto = torch.from_numpy(proto)
        #
        for i, pred in enumerate(p):
            orig_img = orig_imgs[i]
            img_path = self.batch[0][i]
            if not len(pred):  # save empty boxes
                masks = None
            elif self.args.retina_masks:
                pred[:, :4] = ops.scale_boxes(img.shape[2:], pred[:, :4], orig_img.shape)
                masks = ops.process_mask_native(proto[i], pred[:, 6:], pred[:, :4], orig_img.shape[:2])  # HWC
            else:
                masks = ops.process_mask(proto[i], pred[:, 6:], pred[:, :4], img.shape[2:], upsample=True)  # HWC
                pred[:, :4] = ops.scale_boxes(img.shape[2:], pred[:, :4], orig_img.shape)

            # 【兼容】 将框大小x1,y1,x2,y2比例变换为目标图片大小的值
            if len(pred):
                if orig_imgs_l:
                    orig_img_l = orig_imgs_l[i]
                    orig_img_shape = orig_img.shape
                    orig_img_l_shape = orig_img_l.shape
                    wight_rate = orig_img_l_shape[1] / orig_img_shape[1]
                    height_rate = orig_img_l_shape[0] / orig_img_shape[0]
                    pred[:, 0], pred[:, 2] = pred[:, 0] * wight_rate, pred[:, 2] * wight_rate
                    pred[:, 1], pred[:, 3] = pred[:, 1] * height_rate, pred[:, 3] * height_rate
                    # 对掩码进行缩放
                    if masks is not None:

                        # 如果掩码是一个 tensor 格式
                        if isinstance(masks.data, torch.Tensor):
                            # 计算目标尺寸
                            target_size = (int(masks.data.shape[2] * height_rate),
                                           int(masks.data.shape[1] * wight_rate))

                            # 使用 interpolate 进行缩放
                            masks = interpolate(masks.data.unsqueeze(0), size=target_size, mode='bilinear',
                                                align_corners=False).squeeze(0)
                            # 这里的 masks.unsqueeze(0) 是为了增加 batch 维度，interpolate 需要一个 4D tensor (B, C, H, W)
                            # 最后使用 squeeze(0) 去除增加的 batch 维度
            orig_img_l = orig_imgs_l[i] if orig_imgs_l else orig_img
            #
            results.append(Results(orig_img_l, path=img_path, names=self.model.names, boxes=pred[:, :6], masks=masks))
        return results
